package com.neuronbit.businessflow.intellij.editor;

import com.neuronbit.businessflow.intellij.BpmCefRequestHandler;
import com.neuronbit.businessflow.intellij.function.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorLocation;
import com.intellij.openapi.fileEditor.FileEditorState;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.registry.RegistryValue;
import com.intellij.openapi.util.registry.RegistryValueListener;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.jcef.JBCefBrowser;
import com.intellij.ui.jcef.JBCefBrowserBase;
import com.intellij.ui.jcef.JBCefClient;
import com.intellij.ui.jcef.JBCefJSQuery;
import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.handler.CefLoadHandlerAdapter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * The type Bpm html file editor.
 */
public class BpmHtmlFileEditor implements FileEditor, @NotNull RegistryValueListener {
    private final String HOST_NAME = "localhost";
    private final String PROTOCOL = "http";

    private final String VIEWER_URL = PROTOCOL + "://" + HOST_NAME + "/index.html";
    private FileEditorState state;
    private boolean isModified = false;
    private List listeners = new ArrayList();
    private Map userData = new HashMap();

    private Project project;
    private VirtualFile file;
    private JBCefJSQuery jbCefJSQuery;
    private JPanel jPanel;
    private Gson gson = new GsonBuilder().create();
    private Type strMapType = new TypeToken<Map<String, String>>() {
    }.getType();

    private final static String[] css = new String[]{
            "editor/css/index.css"
    };
    private final static String[] js = new String[]{
            "editor/js/index.js"
    };
    private String isDebug = "false";

    /**
     * Instantiates a new Bpm html file editor.
     *
     * @param project the project
     * @param file    the file
     */
    public BpmHtmlFileEditor(@NotNull Project project, VirtualFile file) {
        this.project = project;
        this.file = file;

        final JBCefBrowserBase jbCefBrowser = new JBCefBrowser();

        final CefBrowser cefBrowser = jbCefBrowser.getCefBrowser();

        // Create a JS query instance
        jbCefJSQuery = JBCefJSQuery.create(jbCefBrowser);

        jPanel = new JPanel();
        jPanel.setLayout(new BorderLayout());
        jPanel.add(jbCefBrowser.getComponent(), BorderLayout.CENTER);

        JBCefClient jbCefClient = jbCefBrowser.getJBCefClient();
        jbCefClient.addRequestHandler(new BpmCefRequestHandler(PROTOCOL, HOST_NAME, this), cefBrowser);
        jbCefClient.addRequestHandler(new BpmCefRequestHandler(PROTOCOL, "at.alicdn.com", this), cefBrowser);

        //调用不能在 LoadHandler 的 onLoadEnd 方法里面，否则会报illegalStateException 的错
        JBCefJSQuery filterJavaClazz = JBCefJSQuery.create(jbCefBrowser); // 1
        filterJavaClazz.addHandler(new ClazzFilterFunction(project, jbCefBrowser.getCefBrowser(), VIEWER_URL));

        JBCefJSQuery filterJavaMethod = JBCefJSQuery.create(jbCefBrowser); // 1
        filterJavaMethod.addHandler(new MethodFilterFunction(project, jbCefBrowser.getCefBrowser(), VIEWER_URL));

        JBCefJSQuery filterJavaParam = JBCefJSQuery.create(jbCefBrowser); // 1
        filterJavaParam.addHandler(new ParamFilterFunction(project, jbCefBrowser.getCefBrowser(), VIEWER_URL));

        JBCefJSQuery saveSchema = JBCefJSQuery.create(jbCefBrowser); // 1
        saveSchema.addHandler(new SaveSchemaFunction(project, file, jbCefBrowser.getCefBrowser(), VIEWER_URL));

        JBCefJSQuery readSchema = JBCefJSQuery.create(jbCefBrowser); // 1
        readSchema.addHandler(new ReadSchemaFunction(project, file, jbCefBrowser.getCefBrowser(), VIEWER_URL));

        jbCefClient.addLoadHandler(new CefLoadHandlerAdapter() {
            @Override
            public void onLoadEnd(CefBrowser browser, CefFrame frame, int httpStatusCode) {
                //executeJavaScript必须在 onLoadEnd 里面调用，否则没有效果
//                jbCefBrowser.getCefBrowser().executeJavaScript("alert('Hello World!')",
//                        VIEWER_URL
//                        , 0);

                jbCefBrowser.getCefBrowser().executeJavaScript( // 4
                        "window.filterJavaClazz = function(params) { " +
                                filterJavaClazz.inject("params",
                                        "function(response) {" +
                                                "console.log(response);" +
//                                                "window.AliLowCodeEngine.event.emit('ClazzEvent', response);"+
                                                "}",
                                        "function(error_code, error_message) {}") + // 5
                                "};",
                        VIEWER_URL, 0
                );

                jbCefBrowser.getCefBrowser().executeJavaScript( // 4
                        "window.filterJavaMethod = function(params) { " +
                                filterJavaMethod.inject("params",
                                        "function(response) {" +
                                                "console.log(response);" +
//                                                "window.AliLowCodeEngine.event.emit('ClazzEvent', response);"+
                                                "}",
                                        "function(error_code, error_message) {}") + // 5
                                "};",
                        VIEWER_URL, 0
                );

                jbCefBrowser.getCefBrowser().executeJavaScript( // 4
                        "window.filterJavaParam = function(params) { " +
                                filterJavaParam.inject("params",
                                        "function(response) {" +
                                                "console.log(response);" +
//                                                "window.AliLowCodeEngine.event.emit('ClazzEvent', response);"+
                                                "}",
                                        "function(error_code, error_message) {}") + // 5
                                "};",
                        VIEWER_URL, 0
                );

                jbCefBrowser.getCefBrowser().executeJavaScript( // 4
                        "window.saveSchema = function(params) { " +
                                saveSchema.inject("params",
                                        "function(response) {" +
                                                "console.log(response);" +
//                                                "window.AliLowCodeEngine.event.emit('ClazzEvent', response);"+
                                                "}",
                                        "function(error_code, error_message) {}") + // 5
                                "};",
                        VIEWER_URL, 0
                );

                jbCefBrowser.getCefBrowser().executeJavaScript( // 4
                        "window.readSchema = function(params) { " +
                                readSchema.inject("params",
                                        "function(response) {" +
                                                "console.log(response);" +
//                                                "window.AliLowCodeEngine.event.emit('ClazzEvent', response);"+
                                                "}",
                                        "function(error_code, error_message) {}") + // 5
                                "};",
                        VIEWER_URL, 0
                );

                jbCefBrowser.getCefBrowser().executeJavaScript( // 6
                        """
                                console.log('going to load schema');
                                window.readSchema();
                                """,
                        VIEWER_URL, 0
                );

//                jbCefBrowser.getCefBrowser().executeJavaScript( // 6
//                        """
//                        document.addEventListener('click', function (e) {
//                             window.callJava('{event:\\'xxxx\\', pa:\\'x\\'}');
//                        });
//                        """,
//                        VIEWER_URL, 0
//                );
            }
        }, cefBrowser);

//        RegistryValue value = Registry.get("ide.browser.jcef.svg-viewer.debug");
//        isDebug = value.getSelectedOption();
//        value.addListener(this, this);

        final BpmHtmlFileEditor editor = this;

//        Disposer.register(this, myUIComponent)
//        Disposer.register(this, myBrowser)

        if (isDebugMode()) {
            jbCefBrowser.loadURL(VIEWER_URL + "?debug");
        } else {
            jbCefBrowser.loadURL(VIEWER_URL);
        }
    }

    private boolean isDebugMode() {
        return isDebug.equalsIgnoreCase("true");
    }

    @Override
    public VirtualFile getFile() {
        return file;
    }

    /**
     * @return component which represents editor in the UI.
     * The method should never return {@code null}.
     */
    @Override
    public @NotNull JComponent getComponent() {
        return jPanel;
    }

    /**
     * Returns component to be focused when editor is opened.
     */
    @Override
    public @Nullable JComponent getPreferredFocusedComponent() {
        //todo implement it
        return null;
    }

    /**
     * @return editor's name, a string that identifies editor among
     * other editors. For example, UI form might have two editor: "GUI Designer"
     * and "Text". So "GUI Designer" can be a name of one editor and "Text"
     * can be a name of other editor. The method should never return {@code null}.
     */
    @Override
    public @NotNull String getName() {
        return "HTML Designer";
    }

    /**
     * Applies given state to the editor.
     *
     * @param state cannot be null
     */
    @Override
    public void setState(@NotNull FileEditorState state) {
        this.state = state;
    }

    /**
     * @return whether the editor's content is modified in comparison with its file.
     */
    @Override
    public boolean isModified() {
        return this.isModified;
    }

    /**
     * @return whether the editor is valid or not. An editor is valid if the contents displayed in it still exists. For example, an editor
     * displaying the contents of a file stops being valid if the file is deleted. Editor can also become invalid when it's disposed.
     */
    @Override
    public boolean isValid() {
        //todo implement it
        return true;
    }

    /**
     * Removes specified listener
     *
     * @param listener to be added
     */
    @Override
    public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
        this.listeners.add(listener);
    }

    /**
     * Adds specified listener
     *
     * @param listener to be removed
     */
    @Override
    public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
        this.listeners.remove(listener);
    }

    /**
     * The method is optional. Currently is used only by find usages subsystem
     *
     * @return the location of user focus. Typically it's a caret or any other form of selection start.
     */
    @Override
    public @Nullable FileEditorLocation getCurrentLocation() {
        //todo implement it
        return null;
    }

    /**
     * Usually not invoked directly, see class javadoc.
     */
    @Override
    public void dispose() {
        //todo implement it
        // Dispose the query when necessary
        Disposer.dispose(jbCefJSQuery);
    }

    /**
     * @param key
     * @return a user data value associated with this object. Doesn't require read action.
     */
    @Override
    public <T> @Nullable T getUserData(@NotNull Key<T> key) {
        return (T) this.userData.get(key);
    }

    /**
     * Add a new user data value to this object. Doesn't require write action.
     *
     * @param key
     * @param value
     */
    @Override
    public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
        this.userData.put(key, value);
    }

    @Override
    public void afterValueChanged(@NotNull RegistryValue value) {
        this.isDebug = value.getSelectedOption();
    }

}
